/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// H.264 Video RTP Sources
// Implementation

#include "include/H264VideoRTPSource.hh"
#include "include/Base64.hh"

////////// H264BufferedPacket and H264BufferedPacketFactory //////////

class H264BufferedPacket : public BufferedPacket {
public:
    H264BufferedPacket(H264VideoRTPSource &ourSource);

    virtual ~H264BufferedPacket();

private: // redefined virtual functions
    virtual unsigned nextEnclosedFrameSize(unsigned char *&framePtr,
                                           unsigned dataSize);

private:
    H264VideoRTPSource &fOurSource;
};

class H264BufferedPacketFactory : public BufferedPacketFactory {
private: // redefined virtual functions
    virtual BufferedPacket *createNewPacket(MultiFramedRTPSource *ourSource);
};


///////// H264VideoRTPSource implementation ////////

H264VideoRTPSource *
H264VideoRTPSource::createNew(UsageEnvironment &env, Groupsock *RTPgs,
                              unsigned char rtpPayloadFormat,
                              unsigned rtpTimestampFrequency) {
    return new H264VideoRTPSource(env, RTPgs, rtpPayloadFormat,
                                  rtpTimestampFrequency);
}

H264VideoRTPSource
::H264VideoRTPSource(UsageEnvironment &env, Groupsock *RTPgs,
                     unsigned char rtpPayloadFormat,
                     unsigned rtpTimestampFrequency)
        : MultiFramedRTPSource(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency,
                               new H264BufferedPacketFactory) {
}

H264VideoRTPSource::~H264VideoRTPSource() {
}

Boolean H264VideoRTPSource
::processSpecialHeader(BufferedPacket *packet,
                       unsigned &resultSpecialHeaderSize) {
    unsigned char *headerStart = packet->data();
    unsigned packetSize = packet->dataSize();
    unsigned numBytesToSkip;

    // Check the 'nal_unit_type' for special 'aggregation' or 'fragmentation' packets:
    if (packetSize < 1) return False;
    fCurPacketNALUnitType = (headerStart[0] & 0x1F);
    switch (fCurPacketNALUnitType) {
        case 24: { // STAP-A
            numBytesToSkip = 1; // discard the type byte
            break;
        }
        case 25:
        case 26:
        case 27: { // STAP-B, MTAP16, or MTAP24
            numBytesToSkip = 3; // discard the type byte, and the initial DON
            break;
        }
        case 28:
        case 29: { // // FU-A or FU-B
            // For these NALUs, the first two bytes are the FU indicator and the FU header.
            // If the start bit is set, we reconstruct the original NAL header into byte 1:
            if (packetSize < 2) return False;
            unsigned char startBit = headerStart[1] & 0x80;
            unsigned char endBit = headerStart[1] & 0x40;
            if (startBit) {
                fCurrentPacketBeginsFrame = True;

                headerStart[1] = (headerStart[0] & 0xE0) | (headerStart[1] & 0x1F);
                numBytesToSkip = 1;
            } else {
                // The start bit is not set, so we skip both the FU indicator and header:
                fCurrentPacketBeginsFrame = False;
                numBytesToSkip = 2;
            }
            fCurrentPacketCompletesFrame = (endBit != 0);
            break;
        }
        default: {
            // This packet contains one complete NAL unit:
            fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame = True;
            numBytesToSkip = 0;
            break;
        }
    }

    resultSpecialHeaderSize = numBytesToSkip;
    return True;
}

char const *H264VideoRTPSource::MIMEtype() const {
    return "video/H264";
}

SPropRecord *parseSPropParameterSets(char const *sPropParameterSetsStr,
        // result parameter:
                                     unsigned &numSPropRecords) {
    // Make a copy of the input string, so we can replace the commas with '\0's:
    char *inStr = strDup(sPropParameterSetsStr);
    if (inStr == NULL) {
        numSPropRecords = 0;
        return NULL;
    }

    // Count the number of commas (and thus the number of parameter sets):
    numSPropRecords = 1;
    char *s;
    for (s = inStr; *s != '\0'; ++s) {
        if (*s == ',') {
            ++numSPropRecords;
            *s = '\0';
        }
    }

    // Allocate and fill in the result array:
    SPropRecord *resultArray = new SPropRecord[numSPropRecords];
    s = inStr;
    for (unsigned i = 0; i < numSPropRecords; ++i) {
        resultArray[i].sPropBytes = base64Decode(s, resultArray[i].sPropLength);
        s += strlen(s) + 1;
    }

    delete[] inStr;
    return resultArray;
}


////////// H264BufferedPacket and H264BufferedPacketFactory implementation //////////

H264BufferedPacket::H264BufferedPacket(H264VideoRTPSource &ourSource)
        : fOurSource(ourSource) {
}

H264BufferedPacket::~H264BufferedPacket() {
}

unsigned H264BufferedPacket
::nextEnclosedFrameSize(unsigned char *&framePtr, unsigned dataSize) {
    unsigned resultNALUSize = 0; // if an error occurs

    switch (fOurSource.fCurPacketNALUnitType) {
        case 24:
        case 25: { // STAP-A or STAP-B
            // The first two bytes are NALU size:
            if (dataSize < 2) break;
            resultNALUSize = (framePtr[0] << 8) | framePtr[1];
            framePtr += 2;
            break;
        }
        case 26: { // MTAP16
            // The first two bytes are NALU size.  The next three are the DOND and TS offset:
            if (dataSize < 5) break;
            resultNALUSize = (framePtr[0] << 8) | framePtr[1];
            framePtr += 5;
            break;
        }
        case 27: { // MTAP24
            // The first two bytes are NALU size.  The next four are the DOND and TS offset:
            if (dataSize < 6) break;
            resultNALUSize = (framePtr[0] << 8) | framePtr[1];
            framePtr += 6;
            break;
        }
        default: {
            // Common case: We use the entire packet data:
            return dataSize;
        }
    }

    return (resultNALUSize <= dataSize) ? resultNALUSize : dataSize;
}

BufferedPacket *H264BufferedPacketFactory
::createNewPacket(MultiFramedRTPSource *ourSource) {
    return new H264BufferedPacket((H264VideoRTPSource &) (*ourSource));
}
